#include "music_constraints.h"

#include <QList>
#include <QRegExp>
#include <QString>
#include <QStringList>

#include <gecode/int/branch.hh>

#include "core/music_settings.h"
#include "core/unique_random_branching.h"

MusicConstraints::MusicConstraints(MusicSettings *settings):
  settings(settings),
  /*
   * Une partition est une suite de notes de taille n
   * n correspond ici l'attribut scoreSize
   *
   * Un note est représentée par deux entiers :
   *  - le pitch (hauteur) compris entre 1 et 12
   *  - l'octave compris entre 1 et la valeur nbOctave configurée par
   *    l'utilisateur
   *
   * Pour chaque valeur de la hauteur correspond à une note de la gamme
   * (une touche d'un clavier de piano par exemple) :
   *  -  1 -> do
   *  -  2 -> do dièse
   *  -  3 -> ré
   *  -  4 -> ré dièse
   *  -  5 -> mi
   *  -  6 -> fa
   *  -  7 -> fa dièse
   *  -  8 -> sol
   *  -  9 -> sol dièse
   *  - 10 -> la
   *  - 11 -> la dièse
   *  - 12 -> si
   */
  pitch(this, settings->scoreSize, 1, 12),
  octave(this, settings->scoreSize, 1, settings->nbOctave) {

  // contraintes « mode et la tonalité »
  if(settings->mode != MusicSettings::UNDEFINED_MODE)
    scaleConstraint();

  // contrainte « notes conjointes »
  if(settings->conjoint)
    conjointConstraint();

  // contrainte « pas de note répétée »
  if(settings->noRepetition)
    noRepetitionConstraint();

  // contrainte « motif »
  QRegExp re;

  // motif simple
  re = QRegExp("[UDR]+");
  if(re.exactMatch(settings->pattern))
    simplePatternConstraint();

  // motif avancé
  re = QRegExp("([UD]\\d+|R)+");
  if(re.exactMatch(settings->pattern))
    advancedPatternConstraint();

  // contrainte « première note »
  if(settings->firstNote != MusicSettings::UNDEFINED_DEGREE)
    firstNote();

  // branchement aléatoire
  /*
   * Permet de chercher une solution de mélodie possible
   * de façon aléatoire en respectant les contraintes
   */
  UniqueRandomBranching::post(this, pitch);
  UniqueRandomBranching::post(this, octave);
}

MusicConstraints::MusicConstraints(bool share, MusicConstraints &s):
  Space(share, s),
  settings(s.settings) {
  pitch.update(this, share, s.pitch);
  octave.update(this, share, s.octave);
}

Space * MusicConstraints::copy(bool share) {
  return new MusicConstraints(share, *this);
}

/*!
 * Écrit les données du CSP dans un fichier au format Lilypond
 */
void MusicConstraints::writeLy(QString filename, LilyPondWriter::Usage usage) {
  LilyPondWriter lyw(settings, this);
  lyw.write(filename, usage);
}

/*!
 * Définit la contraite « mode majeur » et la tonalité choisie
 */
void MusicConstraints::scaleConstraint() {
  /*
   * La contrainte de gamme consiste à supprimer simplement les notes
   * « non autorisée » de la gamme choisie
   */

  /* 
   * Le demi-ton chromatique de la note sensible des gammes mineures est
   * volontairement « oubliée » ici afin de simplifier le calcul pour les
   * contraintes
   * La note sensible est alors corrigée lors de la conversion au
   * format Lilypond afin d'obtenir un résultat correct
   */
  for(int i = 0; i < settings->scoreSize; i++) {

    // gamme sans altération à la clé

    if((settings->mode == MusicSettings::MAJOR &&
        settings->scale == MusicSettings::C) || // do majeur
       (settings->mode == MusicSettings::MINOR &&
        settings->scale == MusicSettings::A)) { // la mineur
      rel(this, pitch[i], IRT_NQ, 2);
      rel(this, pitch[i], IRT_NQ, 4);
      rel(this, pitch[i], IRT_NQ, 7);
      rel(this, pitch[i], IRT_NQ, 9);
      rel(this, pitch[i], IRT_NQ, 11);
    }

    // gammes ayant des dièses à la clé

    // 1 dièse
    else if((settings->mode == MusicSettings::MAJOR &&
             settings->scale == MusicSettings::G) || // sol majeur
            (settings->mode == MusicSettings::MINOR &&
             settings->scale == MusicSettings::E)) { // mi mineur
      rel(this, pitch[i], IRT_NQ, 2);
      rel(this, pitch[i], IRT_NQ, 4);
      rel(this, pitch[i], IRT_NQ, 6);
      rel(this, pitch[i], IRT_NQ, 9);
      rel(this, pitch[i], IRT_NQ, 11);
    }

    // 2 dièses
    else if((settings->mode == MusicSettings::MAJOR &&
             settings->scale == MusicSettings::D) || // ré majeur
            (settings->mode == MusicSettings::MINOR &&
             settings->scale == MusicSettings::B)) { // si mineur
      rel(this, pitch[i], IRT_NQ, 1);
      rel(this, pitch[i], IRT_NQ, 4);
      rel(this, pitch[i], IRT_NQ, 6);
      rel(this, pitch[i], IRT_NQ, 9);
      rel(this, pitch[i], IRT_NQ, 11);
    }

    // 3 dièses
    else if((settings->mode == MusicSettings::MAJOR &&
             settings->scale == MusicSettings::A) || // la majeur
            (settings->mode == MusicSettings::MINOR &&
             settings->scale == MusicSettings::FIS)) { // fa dièse mineur
      rel(this, pitch[i], IRT_NQ, 1);
      rel(this, pitch[i], IRT_NQ, 4);
      rel(this, pitch[i], IRT_NQ, 6);
      rel(this, pitch[i], IRT_NQ, 8);
      rel(this, pitch[i], IRT_NQ, 11);
    }

    // 4 dièses
    else if((settings->mode == MusicSettings::MAJOR &&
             settings->scale == MusicSettings::E) || // mi majeur
            (settings->mode == MusicSettings::MINOR &&
             settings->scale == MusicSettings::CIS)) { // c dièse mineur
      rel(this, pitch[i], IRT_NQ, 1);
      rel(this, pitch[i], IRT_NQ, 3);
      rel(this, pitch[i], IRT_NQ, 6);
      rel(this, pitch[i], IRT_NQ, 8);
      rel(this, pitch[i], IRT_NQ, 11);
    }

    // 5 dièses
    else if((settings->mode == MusicSettings::MAJOR &&
             settings->scale == MusicSettings::B) || // si majeur
            (settings->mode == MusicSettings::MINOR &&
             settings->scale == MusicSettings::GIS)) { // sol dièse mineur
      rel(this, pitch[i], IRT_NQ, 1);
      rel(this, pitch[i], IRT_NQ, 3);
      rel(this, pitch[i], IRT_NQ, 6);
      rel(this, pitch[i], IRT_NQ, 8);
      rel(this, pitch[i], IRT_NQ, 10);
    }

    // 6 dièses
    else if((settings->mode == MusicSettings::MAJOR &&
             settings->scale == MusicSettings::FIS) || // fa dièse majeur
            (settings->mode == MusicSettings::MINOR &&
             settings->scale == MusicSettings::DIS)) { // ré dièse mineur
      rel(this, pitch[i], IRT_NQ, 1);
      rel(this, pitch[i], IRT_NQ, 3);
      rel(this, pitch[i], IRT_NQ, 5);
      rel(this, pitch[i], IRT_NQ, 8);
      rel(this, pitch[i], IRT_NQ, 10);
    }

    // 7 dièses
    else if((settings->mode == MusicSettings::MAJOR &&
             settings->scale == MusicSettings::CIS) || // co dièse majeur
            (settings->mode == MusicSettings::MINOR &&
             settings->scale == MusicSettings::AIS)) { // la dièse mineur
      rel(this, pitch[i], IRT_NQ, 3);
      rel(this, pitch[i], IRT_NQ, 5);
      rel(this, pitch[i], IRT_NQ, 8);
      rel(this, pitch[i], IRT_NQ, 10);
      rel(this, pitch[i], IRT_NQ, 12);
    }

    // gammes ayant des bémols à la clé

    // 1 bémol
    else if((settings->mode == MusicSettings::MAJOR &&
             settings->scale == MusicSettings::F) || // fa majeur
            (settings->mode == MusicSettings::MINOR &&
             settings->scale == MusicSettings::D)) { // ré mineur
      rel(this, pitch[i], IRT_NQ, 2);
      rel(this, pitch[i], IRT_NQ, 4);
      rel(this, pitch[i], IRT_NQ, 7);
      rel(this, pitch[i], IRT_NQ, 9);
      rel(this, pitch[i], IRT_NQ, 12);
    }

    // 2 bémols
    else if((settings->mode == MusicSettings::MAJOR &&
             settings->scale == MusicSettings::BES) || // si bémol majeur
            (settings->mode == MusicSettings::MINOR &&
             settings->scale == MusicSettings::G)) { // sol mineur
      rel(this, pitch[i], IRT_NQ, 2);
      rel(this, pitch[i], IRT_NQ, 5);
      rel(this, pitch[i], IRT_NQ, 7);
      rel(this, pitch[i], IRT_NQ, 9);
      rel(this, pitch[i], IRT_NQ, 12);
    }

    // 3 bémols
    else if((settings->mode == MusicSettings::MAJOR &&
             settings->scale == MusicSettings::EES) || // mi bémol majeur
            (settings->mode == MusicSettings::MINOR &&
             settings->scale == MusicSettings::C)) { // do mineur
      rel(this, pitch[i], IRT_NQ, 2);
      rel(this, pitch[i], IRT_NQ, 5);
      rel(this, pitch[i], IRT_NQ, 7);
      rel(this, pitch[i], IRT_NQ, 10);
      rel(this, pitch[i], IRT_NQ, 12);
    }

    // 4 bémols
    else if((settings->mode == MusicSettings::MAJOR &&
             settings->scale == MusicSettings::AES) || // la bémol majeur
            (settings->mode == MusicSettings::MINOR &&
             settings->scale == MusicSettings::F)) { // fa mineur
      rel(this, pitch[i], IRT_NQ, 3);
      rel(this, pitch[i], IRT_NQ, 5);
      rel(this, pitch[i], IRT_NQ, 7);
      rel(this, pitch[i], IRT_NQ, 10);
      rel(this, pitch[i], IRT_NQ, 12);
    }

    // 5 bémols
    else if((settings->mode == MusicSettings::MAJOR &&
             settings->scale == MusicSettings::DES) || // ré bémol majeur
            (settings->mode == MusicSettings::MINOR &&
             settings->scale == MusicSettings::BES)) { // si bémol mineur
      rel(this, pitch[i], IRT_NQ, 3);
      rel(this, pitch[i], IRT_NQ, 5);
      rel(this, pitch[i], IRT_NQ, 8);
      rel(this, pitch[i], IRT_NQ, 10);
      rel(this, pitch[i], IRT_NQ, 12);
    }

    // 6 bémols
    else if((settings->mode == MusicSettings::MAJOR &&
             settings->scale == MusicSettings::GES) || // sol bémol majeur
            (settings->mode == MusicSettings::MINOR &&
             settings->scale == MusicSettings::EES)) { // mi bémol mineur
      rel(this, pitch[i], IRT_NQ, 1);
      rel(this, pitch[i], IRT_NQ, 3);
      rel(this, pitch[i], IRT_NQ, 5);
      rel(this, pitch[i], IRT_NQ, 8);
      rel(this, pitch[i], IRT_NQ, 10);
    }

    // 7 bémols
    else if((settings->mode == MusicSettings::MAJOR &&
             settings->scale == MusicSettings::CES) || // do bémol majeur
            (settings->mode == MusicSettings::MINOR &&
             settings->scale == MusicSettings::AES)) { // la bémol mineur
      rel(this, pitch[i], IRT_NQ, 1);
      rel(this, pitch[i], IRT_NQ, 3);
      rel(this, pitch[i], IRT_NQ, 6);
      rel(this, pitch[i], IRT_NQ, 8);
      rel(this, pitch[i], IRT_NQ, 10);
    }
  }
}

/*!
 * Contrainte pour avoir des notes conjointes
 */
void MusicConstraints::conjointConstraint() {
  /*
   * Deux notes conjointes sont des notes qui se suivent dans
   * l'ordre de la gamme
   * Les notes conjointes sont séparées d'un ton ou d'un demi-ton
   * Ainsi la différence de hauteur entre toutes les notes naturelles conjointes
   * est de 1 ton sauf entre mi-fa et si-do (1/2 ton)
   */
  for(int i = 0; i < settings->scoreSize - 1; i++) {
    IntVar tmp1(this, 12, 12 * settings->nbOctave);
    IntVar tmp2(this, 12, 12 * settings->nbOctave);
    IntVar t(this, 12, 12);

    mult(this, octave[i], t, tmp1);
    mult(this, octave[i+1], t, tmp2);

    IntVar m(this, -2, 2);
    post(this, ((pitch[i] + tmp1) - (pitch[i+1] + tmp2)) == m);
  }
}

/*!
 * Contrainte pour ne pas avoir de notes répétées
 */
void MusicConstraints::noRepetitionConstraint() {
  /*
   * Deux notes sont répétées si elles ont la même hauteur et
   * sont à la même octave
   */
  for(int i = 0; i < settings->scoreSize - 1 ; i++) {
    /*
     * Peut être très long à calculer si le nombre d'octave
     * est supérieur à 1
     */
    /*IntVar tmp1(this, 12, 12 * settings->nbOctave);
    IntVar tmp2(this, 12, 12 * settings->nbOctave);
    IntVar t(this, 12, 12);

    mult(this, octave[i], t, tmp1);
    mult(this, octave[i+1], t, tmp2);

    post(this, (pitch[i] + tmp1) != (pitch[i+1] + tmp2));*/

    /*
     * Version simplifiée qui ne tient pas compte de l'octave
     * et qui est beaucoup plus rapide à calculer
     */
    post(this, pitch[i] != pitch[i+1]);
  }
}

/*!
 * Contrainte pour motif simple
 */
void MusicConstraints::simplePatternConstraint() {
  /*
   * Permet de créer un motif dans la mélodie en utilisant le code Parsons
   * http://fr.wikipedia.org/wiki/Code_Parsons
   *
   * U : la note suivante est plus haute que la note courante
   * D : la note suivante est plus basse que la note courante
   * R : la note suivante est à la même hauteur que la note courante
   */
  int j = 0;
  for(int i = 0; i < settings->scoreSize - 1; i++) {
    IntVar tmp1(this, 12, 12 * settings->nbOctave);
    IntVar tmp2(this, 12, 12 * settings->nbOctave);
    IntVar t(this, 12, 12);

    mult(this, octave[i], t, tmp1);
    mult(this, octave[i+1], t, tmp2);

    if(settings->pattern[j] == 'U')
      post(this, (pitch[i] + tmp1) < (pitch[i+1] + tmp2));
    else if(settings->pattern[j] == 'D')
      post(this, (pitch[i] + tmp1) > (pitch[i+1] + tmp2));
    else if(settings->pattern[j] == 'R')
      post(this, (pitch[i] + tmp1) == (pitch[i+1] + tmp2));

    if(j == settings->pattern.size() - 1)
      j = 0;
    else
      j++;
  }
}

/*!
 * Contrainte pour motif avancé
 */
void MusicConstraints::advancedPatternConstraint() {
  /*
   * Cette contrainte reprend le principe précédent sauf 
   * qu'il est possible de spécifier la taille maximale de l'intervalle qui
   * sépare la note courante de la note suivante
   * Exemples :
   *  - U1 : la note suivante est séparée par un ton au maximum de la note
   *         courante
   *  - U2 : la note suivante est séparée par deux tons au maximum de la note
   *         courante
   *  - ...
   */
  QList<int> pos;

  int j = 0;
  while ((j = settings->pattern.indexOf(QRegExp("[UD]\\d+|R"), j)) != -1) {
    pos.append(j);
    ++j;
  }
  pos.append(settings->pattern.size());

  QStringList patternList;
  for(int i = 0; i < pos.size() - 1; i++)
     patternList.append(settings->pattern.mid(pos[i], pos[i+1] - pos[i]));

  int k = 0;
  for(int i = 0; i < settings->scoreSize - 1; i++) {
    IntVar tmp1(this, 12, 12 * settings->nbOctave);
    IntVar tmp2(this, 12, 12 * settings->nbOctave);
    IntVar t(this, 12, 12);

    mult(this, octave[i], t, tmp1);
    mult(this, octave[i+1], t, tmp2);

    if(patternList[k][0] == 'U') {
      int interval = patternList[k].right(patternList[k].size() - 1).toInt();
      IntVar m(this, 1, interval * 2);
      post(this, ((pitch[i+1] + tmp2) - (pitch[i] + tmp1)) == m);
    }
    else if(patternList[k][0] == 'D') {
      int interval = patternList[k].right(patternList[k].size() - 1).toInt();
      IntVar m(this, 1, interval * 2);
      post(this, ((pitch[i] + tmp1) - (pitch[i+1] + tmp2)) == m);
    }
    else if(patternList[k][0] == 'R')
      post(this, (pitch[i] + tmp1) == (pitch[i+1] + tmp2));

    if(k == patternList.size() - 1)
      k = 0;
    else
      k++;
  }
}

/*!
 * Contrainte pour spécifier le degré de la première note
 */
void MusicConstraints::firstNote() {
  /*
   * Permet de spécifier la première note de la mélodie
   * en fonction du mode, de la tonalité et du degré choisi
   */
  int first = 0;

  switch(settings->scale) {
    case MusicSettings::UNDEFINED_SCALE:
      return; // cette contrainte n'a pas d'intérêt si la gamme
              // n'est pas définie

    case MusicSettings::A:
      first = 10;
      break;
    case MusicSettings::B:
      first = 12;
      break;
    case MusicSettings::C:
      first = 1;
      break;
    case MusicSettings::D:
      first = 3;
      break;
    case MusicSettings::E:
      first = 5;
      break;
    case MusicSettings::F:
      first = 6;
      break;
    case MusicSettings::G:
      first = 8;
      break;

    case MusicSettings::AIS:
      first = 11;
      break;
    case MusicSettings::CIS:
      first = 2;
      break;
    case MusicSettings::DIS:
      first = 4;
      break;
    case MusicSettings::FIS:
      first = 7;
      break;
    case MusicSettings::GIS:
      first = 9;
      break;

    case MusicSettings::AES:
      first = 9;
      break;
    case MusicSettings::BES:
      first = 11;
      break;
    case MusicSettings::CES:
      first = 12;
      break;
    case MusicSettings::DES:
      first = 2;
      break;
    case MusicSettings::EES:
      first = 4;
      break;
    case MusicSettings::GES:
      first = 7;
      break;
  }

  int v = first + MusicSettings::degreeToInt(settings->firstNote,
                                             settings->mode) - 1;
  while(v > 12)
    v -= 12;

  rel(this, pitch[0], IRT_EQ, v);
}
